package NG::Sys::Cached::DBRoll;
use strict;
use warnings;
use utf8;

use JSON::XS;

use NG::Sys::Cached::Common;
our @ISA = qw(NG::Sys::Cached::Common::Parent);

sub init {
	my $self = shift;
	$self->SUPER::init;
	unless($self->{ekey}){
		die "need ekey\n";
	}
	unless($self->{pkey}){
		die "need pkey\n";
	}
	unless($self->{nkey}){
		die "need nkey\n";
	}
	unless($self->{fields}){
		die "need fields\n";
	}
	unless($self->{table}){
		die "need table\n";
	}
	unless($self->{dbh}){
		die "need dbh\n";
	}

	@{$self->{hash_fields}}{@{$self->{fields}}} = (1) x @{$self->{fields}};
	if($self->{blobs}){
		$self->{hash_blobs} = { map {$_=>1} $self->check_fields_ary($self->{blobs}) };
	}else{
		$self->{hash_blobs} = {};
	}

	if($self->{hash_fields}{$self->{pkey}}){
		die "pkey '$self->{pkey}' present in fields\n";
	}
	if($self->{hash_fields}{$self->{ekey}}){
		die "ekey '$self->{ekey}' present in fields\n";
	}
	unless($self->{hash_fields}{$self->{nkey}}){
		die "nkey not present in fields\n";
	}

	$self->{record_class} ||= 'NG::Sys::Cached::DBRoll::Record';

	$self->{sth}{insert} = $self->{dbh}->prepare("
		insert into `".$self->{table}."` (".
		"`".$self->{pkey}."`,".
		join(", ",map {"`".$_."`" } @{$self->{fields}}).
		") values (?,?,".
		join(", ",("?") x  @{$self->{fields}}).
		")");
	$self->{sth}{read} = $self->{dbh}->prepare("
		select * from `".$self->{table}."`
		where `".$self->{pkey}."`=?
		");
	$self->{sth}{row} = $self->{dbh}->prepare("
		select * from `".$self->{table}."`
		where `".$self->{pkey}."`=? and `".$self->{nkey}."`=?
		");
	$self->{sth}{remove} = $self->{dbh}->prepare("
		delete from `".$self->{table}."`
		where `".$self->{pkey}."`=?
		");
	$self->{sth}{extract} = $self->{dbh}->prepare("
		delete from `".$self->{table}."`
		where `".$self->{pkey}."`=? and `".$self->{nkey}."`=?
		");
	$self->{sth}{count} = $self->{dbh}->prepare("
		select count(*) from `".$self->{table}."`
		where `".$self->{pkey}."`=?
		");
}
sub check_field {
	my $self = shift;
	my $key = shift;
	return exists $self->{hash_fields}{$key} || die "unexists key'".$key."' checked\n";
}
sub check_fields_ary {
	my ($self,$keys) = @_;
	my $h = $self->{hash_fields};
	exists $h->{$_} || die "unexists key'".$_."' checked\n" for @$keys;
	return @$keys;
}
sub need_create {
	return undef;
}
sub get_obj {
	my ($self,$id) = @_;
	die "Bad id='$id'" unless $id and 0 < $id and !ref $id;
	return bless {parent=>$self,id=>$id},$self->{record_class};
}
sub insert {
	my $self = shift;
	my $id = shift;
	my $row = shift;
	if(scalar %{$self->{hash_blobs}}){
		for(@{$self->{blobs}}){
			defined($row->{$_}) and $row->{$_} = encode_json($row->{$_});
		}
	}
	$self->{sth}{insert}->execute($id,@{$row}{@{$self->{fields}}});
	my $ret = $self->{sth}{insert}{mysql_insertid};
	$self->clear_cache($id);
	return $ret;
}
sub _get_list {
	my $self = shift;
	my $id = shift;
	
	$self->{sth}{read}->execute($id);
	my $list = $self->{sth}{read}->fetchall_arrayref({});
	if(scalar %{$self->{hash_blobs}}){
		for my $row(@$list){
			for(@{$self->{blobs}}){
				defined $row->{$_} and $row->{$_} = decode_json($row->{$_});
			}
		}
	}
	return $list;
}
sub _read_data {
	my $self= shift;
	my $id = shift;
	$self->{sth}{count}->execute($id);
	my $count = $self->{sth}{count}->fetchrow_array() || 0;
	return $count;
}
sub _get_length {
	my $self = shift;
	my $id = shift;
	my $data;
	if(defined ($data = $self->context->{$id})){
		$self->count_action("context");
		return $data;
	}
	if(defined($data = $self->{cache}->get($id))){
		$self->count_action("cache");
		$self->context->{$id} = $data;
		return $data;
	}
	$data = $self->_read_data($id);
	$self->{cache}->set($id,$data);
	$self->context->{$id} = $data;
	return $data;
}
sub _extract {
	my $self = shift;
	my $id = shift;
	my $name = shift;
	my $ret = $self->{sth}{extract}->execute($id,$name);
	$self->clear_cache($id);
	return $ret;
}
sub _get_row {
	my $self = shift;
	my $id = shift;
	my $name = shift;
	$self->{sth}{row}->execute($id,$name);
	return $self->{sth}{row}->fetchrow_hashref;
}
sub _update_row {
	my $self = shift;
	my $id = shift;
	my $name = shift;
	my $row = shift;
	
	delete $row->{$self->{nkey}};
	my @keys = keys %$row;
	unless(@keys){
		return 0;
	}
	my @skeys = sort @keys;
	my $key = "update:".join(":",@skeys);
	my $sth;
	unless($sth = $self->{sth}{$key}){
		my $sql =
			"update `".$self->{table}."` set ".join(", ", map {'`'.$_.'`=?'} @skeys).
			" where `".$self->{pkey}."`=? and `".$self->{nkey}."`=?";
		$sth = $self->{sth}{$key} = $self->{dbh}->prepare($sql);
	}
	$sth->execute(@{$row}{@skeys},$id,$name);
}

sub _remove {
	my $self = shift;
	my $id = shift;
	$self->{sth}{remove}->execute($id);
	$self->clear_cache($id);
}
package NG::Sys::Cached::DBRoll::Record;
use strict;
use warnings;
use utf8;

use NG::Sys::Cached::Common;
our @ISA = qw(NG::Sys::Cached::Common::Child);

sub obj_id {
	my ($self) = @_;
	return $self->{id};
}
sub insert {
	my $self = shift;
	my $row = shift;
	$self->{parent}->insert($self->{id},$row);
}
sub get_list {
	my $self = shift;
	$self->{parent}->_get_list($self->{id});
}
sub extract {
	my $self = shift;
	my $name = shift;
	$self->{parent}->_extract($self->{id},$name);
}
sub get_row {
	my $self = shift;
	my $name = shift;
	$self->{parent}->_get_row($self->{id},$name);
}
sub update_row {
	my $self = shift;
	my $name = shift;
	my $row = shift;
	unless($row){
		$row = $name;
		$name = $row->{$self->{parent}{nkey}};
	}
	$self->{parent}->_update_row($self->{id},$name,$row);
}
sub remove {
	my $self = shift;
	$self->{parent}->remove($self->{id});
}
sub length {
	my $self = shift;
	return $self->{parent}->_get_length($self->{id});
}


1;
